<!DOCTYPE html>
<html lang="en" dir="auto">

<head><meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta name="robots" content="index, follow">
<title>BARF: Bundle Adjusting Neural Radiance Field | WangJV Blog</title>
<meta name="keywords" content="计算机视觉, 神经渲染, 3D重建, NeRF">
<meta name="description" content="探讨 BARF 方法：如何在未知相机位姿的情况下优化神经辐射场，实现图像对齐和3D重建">
<meta name="author" content="WangJV">
<link rel="canonical" href="https://wangjv0812.github.io/WangJV-Blog-Pages/2024/10/barf-bundle-adjusting-neural-radiance-field/">
<link crossorigin="anonymous" href="https://wangjv0812.github.io/WangJV-Blog-Pages/assets/css/stylesheet.8fe10233a706bc87f2e08b3cf97b8bd4c0a80f10675a143675d59212121037c0.css" integrity="sha256-j&#43;ECM6cGvIfy4Is8&#43;XuL1MCoDxBnWhQ2ddWSEhIQN8A=" rel="preload stylesheet" as="style">
<link rel="icon" href="https://wangjv0812.github.io/WangJV-Blog-Pages/favicon.ico">
<link rel="icon" type="image/png" sizes="16x16" href="https://wangjv0812.github.io/WangJV-Blog-Pages/favicon-16x16.png">
<link rel="icon" type="image/png" sizes="32x32" href="https://wangjv0812.github.io/WangJV-Blog-Pages/favicon-32x32.png">
<link rel="apple-touch-icon" href="https://wangjv0812.github.io/WangJV-Blog-Pages/apple-touch-icon.png">
<link rel="mask-icon" href="https://wangjv0812.github.io/WangJV-Blog-Pages/safari-pinned-tab.svg">
<meta name="theme-color" content="#2e2e33">
<meta name="msapplication-TileColor" content="#2e2e33">
<link rel="alternate" hreflang="en" href="https://wangjv0812.github.io/WangJV-Blog-Pages/2024/10/barf-bundle-adjusting-neural-radiance-field/">
<noscript>
    <style>
        #theme-toggle,
        .top-link {
            display: none;
        }

    </style>
    <style>
        @media (prefers-color-scheme: dark) {
            :root {
                --theme: rgb(29, 30, 32);
                --entry: rgb(46, 46, 51);
                --primary: rgb(218, 218, 219);
                --secondary: rgb(155, 156, 157);
                --tertiary: rgb(65, 66, 68);
                --content: rgb(196, 196, 197);
                --code-block-bg: rgb(46, 46, 51);
                --code-bg: rgb(55, 56, 62);
                --border: rgb(51, 51, 51);
            }

            .list {
                background: var(--theme);
            }

            .list:not(.dark)::-webkit-scrollbar-track {
                background: 0 0;
            }

            .list:not(.dark)::-webkit-scrollbar-thumb {
                border-color: var(--theme);
            }
        }

    </style>
</noscript><script>
  MathJax = {
    tex: {
      displayMath: [['\\[', '\\]'], ['$$', '$$']],
      inlineMath: [['\\(', '\\)'], ['$', '$']],
      processEscapes: true,
      processEnvironments: true,
      tags: 'ams'
    },
    chtml: {
      scale: 1,                     
      minScale: 0.5,               
      matchFontHeight: false,       
      displayAlign: 'center',       
      displayIndent: '0',           
      mtextInheritFont: false,      
      merrorInheritFont: true,      
      mathmlSpacing: false,         
      skipHtmlTags: ['script','noscript','style','textarea','pre','code','a'],
      ignoreHtmlClass: 'tex2jax_ignore',
      processHtmlClass: 'tex2jax_process'
    },
    svg: {
      scale: 1,                     
      minScale: 0.5,               
      mtextInheritFont: false,      
      merrorInheritFont: true,
      mathmlSpacing: false,
      skipHtmlTags: ['script','noscript','style','textarea','pre','code','a'],
      ignoreHtmlClass: 'tex2jax_ignore',
      processHtmlClass: 'tex2jax_process'
    },
    options: {
      enableMenu: true,             
      menuOptions: {
        settings: {
          zoom: 'Click'             
        }
      }
    },
    loader: {
      load: ['ui/safe', 'a11y/assistive-mml']
    },
    startup: {
      ready() {
        MathJax.startup.defaultReady();
        
        const observer = new ResizeObserver(entries => {
          MathJax.typesetPromise();
        });
        observer.observe(document.body);
      }
    }
  };
  
  
  if (window.innerWidth <= 768) {
    MathJax.chtml = MathJax.chtml || {};
    MathJax.chtml.scale = 0.9;  
  }
</script>
<script id="MathJax-script" async src="https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-chtml.js"></script>


<style>
   
  .MathJax {
    outline: 0;
  }
  
   
  @media (max-width: 768px) {
    .MathJax {
      font-size: 90% !important;
    }
    
     
    .MathJax_Display {
      overflow-x: auto;
      overflow-y: hidden;
      padding: 0 !important;
      margin: 1em 0 !important;
    }
    
     
    .MathJax_CHTML {
      line-height: 1.2 !important;
    }
  }
  
   
  mjx-container[jax="CHTML"][display="true"] {
    overflow-x: auto;
    overflow-y: hidden;
    padding: 1px 0;
  }
</style><meta property="og:url" content="https://wangjv0812.github.io/WangJV-Blog-Pages/2024/10/barf-bundle-adjusting-neural-radiance-field/">
  <meta property="og:site_name" content="WangJV Blog">
  <meta property="og:title" content="BARF: Bundle Adjusting Neural Radiance Field">
  <meta property="og:description" content="探讨 BARF 方法：如何在未知相机位姿的情况下优化神经辐射场，实现图像对齐和3D重建">
  <meta property="og:locale" content="en-us">
  <meta property="og:type" content="article">
    <meta property="article:section" content="posts">
    <meta property="article:published_time" content="2024-10-17T16:40:25+08:00">
    <meta property="article:modified_time" content="2024-10-17T16:40:25+08:00">
    <meta property="article:tag" content="计算机视觉">
    <meta property="article:tag" content="神经渲染">
    <meta property="article:tag" content="3D重建">
    <meta property="article:tag" content="NeRF">
      <meta property="og:image" content="https://wangjv0812.github.io/WangJV-Blog-Pages/">
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:image" content="https://wangjv0812.github.io/WangJV-Blog-Pages/">
<meta name="twitter:title" content="BARF: Bundle Adjusting Neural Radiance Field">
<meta name="twitter:description" content="探讨 BARF 方法：如何在未知相机位姿的情况下优化神经辐射场，实现图像对齐和3D重建">


<script type="application/ld+json">
{
  "@context": "https://schema.org",
  "@type": "BreadcrumbList",
  "itemListElement": [
    {
      "@type": "ListItem",
      "position":  1 ,
      "name": "Posts",
      "item": "https://wangjv0812.github.io/WangJV-Blog-Pages/posts/"
    }, 
    {
      "@type": "ListItem",
      "position":  2 ,
      "name": "BARF: Bundle Adjusting Neural Radiance Field",
      "item": "https://wangjv0812.github.io/WangJV-Blog-Pages/2024/10/barf-bundle-adjusting-neural-radiance-field/"
    }
  ]
}
</script>
<script type="application/ld+json">
{
  "@context": "https://schema.org",
  "@type": "BlogPosting",
  "headline": "BARF: Bundle Adjusting Neural Radiance Field",
  "name": "BARF: Bundle Adjusting Neural Radiance Field",
  "description": "探讨 BARF 方法：如何在未知相机位姿的情况下优化神经辐射场，实现图像对齐和3D重建",
  "keywords": [
    "计算机视觉", "神经渲染", "3D重建", "NeRF"
  ],
  "articleBody": "1. 图像平面对齐 思考我们有一系列位姿$x$不准确的图像。我们希望找到一个图像的扭曲变换（warp transform）$\\mathcal W(x,p)$ 我们知道，两个平面之间的投影变换可以通过homography变换来描述。变换 $\\mathcal W(x,p)$ 受到参数 $p$控制。我们希望能通过不断修正参数 $p = p + \\Delta p$来修正图像的位姿，实现最终的图像对齐。假设我们有两张照片 $\\mathcal I_1(x), \\mathcal I_2(x)$这个对齐过程可以通过下面这样一个最优指标来描述：\n$$ \\min \\sum_x \\lvert \\mathcal I_1(\\mathcal W(x,p)) - \\mathcal I_2(x) \\rvert^2 $$我们讲对 $p$ 的修正 $\\mathcal W(x,p)$ 带入$\\mathcal I_1(x)$中，并做泰勒展开：\n$$ \\mathcal I_1(W(x,p+\\Delta p)) = \\mathcal I_1(\\mathcal W(x,p)) + J(x,p) \\Delta p $$带入误差形式中，有：\n$$ E(\\Delta p) = \\lvert \\sum_x(\\mathcal I_1(\\mathcal W(x,p)) + J(x,p) \\Delta p - \\mathcal I_2(x))\\rvert^2 $$使用梯度下降，有：\n$$ \\frac{\\partial E(\\Delta p)}{\\partial \\Delta p} =2\\sum_x J^T(x,p) [\\mathcal I_1(\\mathcal W(x,p)) + J(x,p) \\Delta p - \\mathcal I_2(x)] = 0 $$重新整理，有：\n$$ \\sum_x J^T(x,p)J(x,p)\\Delta p = -\\sum_x J^T(x,p)\\left[\\mathcal I_1(\\mathcal W(x,p)) - \\mathcal I_2(x)\\right] $$我们令 $A(x,p) = J^T(x,p)J(x,p)$，有：\n$$ \\Delta p = -A(x,p)^{-1}\\sum_x J^T(x,p)\\left[\\mathcal I_1(\\mathcal W(x,p)) - \\mathcal I_2(x)\\right] $$那么很显然的，只要我们能计算出对 warp transform 的参数的修正的导数 $J^T(x,p)$，就可以求出对位姿的修正。我们用链式法则，有：\n$$ \\mathbf{J(x;p)}=\\frac{\\partial\\mathcal{I}_1(\\mathcal{W}(\\mathbf{x};\\mathbf{p}))}{\\partial\\mathcal{W}(\\mathbf{x};\\mathbf{p})}\\frac{\\partial\\mathcal{W}(\\mathbf{x};\\mathbf{p})}{\\partial\\mathbf{p}} $$我们主要需要求取 $\\frac{\\partial\\mathcal{I}_1(\\mathcal{W}(\\mathbf{x};\\mathbf{p}))}{\\partial\\mathcal{W}(\\mathbf{x};\\mathbf{p})}$。那么后面的工作就很显然来，要想办法求出由 Nerf 渲染出的图像对位置的梯度。\n2. Nerf 的表达和优化 我们假设nerf训练出的 mlp 可以表达为一个函数 $\\mathbf{y}=[\\mathbf{c};\\sigma]^\\top=f(\\mathbf{x};\\boldsymbol{\\Theta})$，其中$\\boldsymbol{\\Theta}$ 是待训练的参数。我们简单的将一个像素点上计算出的参数（该像素点对应的射线上的体渲染参数）记作: $\\{\\mathbf{y}_1,\\ldots,\\mathbf{y}_N\\}$。体渲染方程我们记作一个函数:\n$$ \\hat{\\mathcal{I}}(\\mathbf{u})=g\\left(\\mathbf{y}_1,\\ldots,\\mathbf{y}_N\\right) $$那么整个形式我们可以写出：\n$$ \\hat{\\mathcal{I}}(\\mathbf{u};\\mathbf{p})=g\\Big(f(\\mathcal{W}(z_1\\bar{\\mathbf{u}};\\mathbf{p});\\mathbf{\\Theta}),\\ldots,f(\\mathcal{W}(z_N\\bar{\\mathbf{u}};\\mathbf{p});\\mathbf{\\Theta})\\Big) $$梯度可以写出：\n$$ \\mathbf{J}(\\mathbf{u};\\mathbf{p})=\\sum_{i=1}^N\\frac{\\partial g(\\mathbf{y}_1,\\ldots,\\mathbf{y}_N)}{\\partial\\mathbf{y}_i}\\frac{\\partial\\mathbf{y}_i(\\mathbf{p})}{\\partial\\mathbf{x}_i(\\mathbf{p})}\\frac{\\partial\\mathcal{W}(z_i\\bar{\\mathbf{u}};\\mathbf{p})}{\\partial\\mathbf{p}} $$我们总结一下：\n$\\frac{\\partial g}{\\partial y}$ 是体渲染过程的梯度 $\\frac{\\partial y}{\\partial x}$ 是nerf的mlp神经网络的梯度 $\\frac{\\partial W}{\\partial p}$ 是wrap变换的梯度 ",
  "wordCount" : "157",
  "inLanguage": "en",
  "image": "https://wangjv0812.github.io/WangJV-Blog-Pages/","datePublished": "2024-10-17T16:40:25+08:00",
  "dateModified": "2024-10-17T16:40:25+08:00",
  "author":{
    "@type": "Person",
    "name": "WangJV"
  },
  "mainEntityOfPage": {
    "@type": "WebPage",
    "@id": "https://wangjv0812.github.io/WangJV-Blog-Pages/2024/10/barf-bundle-adjusting-neural-radiance-field/"
  },
  "publisher": {
    "@type": "Organization",
    "name": "WangJV Blog",
    "logo": {
      "@type": "ImageObject",
      "url": "https://wangjv0812.github.io/WangJV-Blog-Pages/favicon.ico"
    }
  }
}
</script>
</head>

<body class="" id="top">
<script>
    if (localStorage.getItem("pref-theme") === "dark") {
        document.body.classList.add('dark');
    } else if (localStorage.getItem("pref-theme") === "light") {
        document.body.classList.remove('dark')
    } else if (window.matchMedia('(prefers-color-scheme: dark)').matches) {
        document.body.classList.add('dark');
    }

</script>

<header class="header">
    <nav class="nav">
        <div class="logo">
            <a href="https://wangjv0812.github.io/WangJV-Blog-Pages/" accesskey="h" title="WangJV Blog (Alt + H)">WangJV Blog</a>
            <div class="logo-switches">
                <button id="theme-toggle" accesskey="t" title="(Alt + T)" aria-label="Toggle theme">
                    <svg id="moon" xmlns="http://www.w3.org/2000/svg" width="24" height="18" viewBox="0 0 24 24"
                        fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"
                        stroke-linejoin="round">
                        <path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"></path>
                    </svg>
                    <svg id="sun" xmlns="http://www.w3.org/2000/svg" width="24" height="18" viewBox="0 0 24 24"
                        fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"
                        stroke-linejoin="round">
                        <circle cx="12" cy="12" r="5"></circle>
                        <line x1="12" y1="1" x2="12" y2="3"></line>
                        <line x1="12" y1="21" x2="12" y2="23"></line>
                        <line x1="4.22" y1="4.22" x2="5.64" y2="5.64"></line>
                        <line x1="18.36" y1="18.36" x2="19.78" y2="19.78"></line>
                        <line x1="1" y1="12" x2="3" y2="12"></line>
                        <line x1="21" y1="12" x2="23" y2="12"></line>
                        <line x1="4.22" y1="19.78" x2="5.64" y2="18.36"></line>
                        <line x1="18.36" y1="5.64" x2="19.78" y2="4.22"></line>
                    </svg>
                </button>
            </div>
        </div>
        <ul id="menu">
            <li>
                <a href="https://wangjv0812.github.io/WangJV-Blog-Pages/" title="Home">
                    <span>Home</span>
                </a>
            </li>
            <li>
                <a href="https://wangjv0812.github.io/WangJV-Blog-Pages/posts/" title="Posts">
                    <span>Posts</span>
                </a>
            </li>
            <li>
                <a href="https://wangjv0812.github.io/WangJV-Blog-Pages/archives/" title="Archive">
                    <span>Archive</span>
                </a>
            </li>
            <li>
                <a href="https://wangjv0812.github.io/WangJV-Blog-Pages/tags/" title="Tags">
                    <span>Tags</span>
                </a>
            </li>
            <li>
                <a href="https://wangjv0812.github.io/WangJV-Blog-Pages/search/" title="🔍 Search (Alt &#43; /)" accesskey=/>
                    <span>🔍 Search</span>
                </a>
            </li>
        </ul>
    </nav>
</header>
<main class="main">

<article class="post-single">
  <header class="post-header">
    <div class="breadcrumbs"><a href="https://wangjv0812.github.io/WangJV-Blog-Pages/">Home</a>&nbsp;»&nbsp;<a href="https://wangjv0812.github.io/WangJV-Blog-Pages/posts/">Posts</a></div>
    <h1 class="post-title entry-hint-parent">
      BARF: Bundle Adjusting Neural Radiance Field
    </h1>
    <div class="post-description">
      探讨 BARF 方法：如何在未知相机位姿的情况下优化神经辐射场，实现图像对齐和3D重建
    </div>
    <div class="post-meta"><span title='2024-10-17 16:40:25 +0800 CST'>October 17, 2024</span>&nbsp;·&nbsp;1 min&nbsp;·&nbsp;157 words&nbsp;·&nbsp;WangJV&nbsp;|&nbsp;<a href="https://github.com/WangJV0812/WangJV-Blog-Source/tree/master/content/posts/BARF:%20Bundle%20Adjusting%20Neural%20Radiance%20Field/index.md" rel="noopener noreferrer edit" target="_blank">Suggest Changes</a>

</div>
  </header> <div class="toc">
    <details >
        <summary accesskey="c" title="(Alt + C)">
            <span class="details">Table of Contents</span>
        </summary>

        <div class="inner"><nav id="TableOfContents">
  <ul>
    <li><a href="#1-图像平面对齐">1. 图像平面对齐</a></li>
    <li><a href="#2-nerf-的表达和优化">2. Nerf 的表达和优化</a></li>
  </ul>
</nav>
        </div>
    </details>
</div>

  <div class="post-content"><h2 id="1-图像平面对齐">1. 图像平面对齐<a hidden class="anchor" aria-hidden="true" href="#1-图像平面对齐">#</a></h2>
<p>思考我们有一系列位姿$x$不准确的图像。我们希望找到一个图像的扭曲变换（warp transform）$\mathcal W(x,p)$ 我们知道，两个平面之间的投影变换可以通过homography变换来描述。变换 $\mathcal W(x,p)$ 受到参数 $p$控制。我们希望能通过不断修正参数 $p = p + \Delta p$来修正图像的位姿，实现最终的图像对齐。假设我们有两张照片 $\mathcal I_1(x), \mathcal I_2(x)$这个对齐过程可以通过下面这样一个最优指标来描述：</p>
$$
\min \sum_x \lvert
    \mathcal I_1(\mathcal W(x,p)) -
    \mathcal I_2(x)
\rvert^2
$$<p>我们讲对 $p$ 的修正 $\mathcal W(x,p)$ 带入$\mathcal I_1(x)$中，并做泰勒展开：</p>
$$
\mathcal I_1(W(x,p+\Delta p)) = \mathcal I_1(\mathcal W(x,p)) + J(x,p) \Delta p
$$<p>带入误差形式中，有：</p>
$$
E(\Delta p) = \lvert \sum_x(\mathcal I_1(\mathcal W(x,p)) + J(x,p) \Delta p - \mathcal I_2(x))\rvert^2
$$<p>使用梯度下降，有：</p>
$$
\frac{\partial E(\Delta p)}{\partial \Delta p} =2\sum_x J^T(x,p) [\mathcal I_1(\mathcal W(x,p)) + J(x,p) \Delta p - \mathcal I_2(x)] = 0
$$<p>重新整理，有：</p>
$$
\sum_x J^T(x,p)J(x,p)\Delta p = -\sum_x J^T(x,p)\left[\mathcal I_1(\mathcal W(x,p)) - \mathcal I_2(x)\right]
$$<p>我们令 $A(x,p) = J^T(x,p)J(x,p)$，有：</p>
$$
\Delta p = -A(x,p)^{-1}\sum_x J^T(x,p)\left[\mathcal I_1(\mathcal W(x,p)) - \mathcal I_2(x)\right]
$$<p>那么很显然的，只要我们能计算出对 warp transform 的参数的修正的导数 $J^T(x,p)$，就可以求出对位姿的修正。我们用链式法则，有：</p>
$$
\mathbf{J(x;p)}=\frac{\partial\mathcal{I}_1(\mathcal{W}(\mathbf{x};\mathbf{p}))}{\partial\mathcal{W}(\mathbf{x};\mathbf{p})}\frac{\partial\mathcal{W}(\mathbf{x};\mathbf{p})}{\partial\mathbf{p}}
$$<p>我们主要需要求取 $\frac{\partial\mathcal{I}_1(\mathcal{W}(\mathbf{x};\mathbf{p}))}{\partial\mathcal{W}(\mathbf{x};\mathbf{p})}$。那么后面的工作就很显然来，要想办法求出由 Nerf 渲染出的图像对位置的梯度。</p>
<h2 id="2-nerf-的表达和优化">2. Nerf 的表达和优化<a hidden class="anchor" aria-hidden="true" href="#2-nerf-的表达和优化">#</a></h2>
<p>我们假设nerf训练出的 mlp 可以表达为一个函数 $\mathbf{y}=[\mathbf{c};\sigma]^\top=f(\mathbf{x};\boldsymbol{\Theta})$，其中$\boldsymbol{\Theta}$ 是待训练的参数。我们简单的将一个像素点上计算出的参数（该像素点对应的射线上的体渲染参数）记作: $\{\mathbf{y}_1,\ldots,\mathbf{y}_N\}$。体渲染方程我们记作一个函数:</p>
$$
\hat{\mathcal{I}}(\mathbf{u})=g\left(\mathbf{y}_1,\ldots,\mathbf{y}_N\right)
$$<p>那么整个形式我们可以写出：</p>
$$
\hat{\mathcal{I}}(\mathbf{u};\mathbf{p})=g\Big(f(\mathcal{W}(z_1\bar{\mathbf{u}};\mathbf{p});\mathbf{\Theta}),\ldots,f(\mathcal{W}(z_N\bar{\mathbf{u}};\mathbf{p});\mathbf{\Theta})\Big)
$$<p>梯度可以写出：</p>
$$
\mathbf{J}(\mathbf{u};\mathbf{p})=\sum_{i=1}^N\frac{\partial g(\mathbf{y}_1,\ldots,\mathbf{y}_N)}{\partial\mathbf{y}_i}\frac{\partial\mathbf{y}_i(\mathbf{p})}{\partial\mathbf{x}_i(\mathbf{p})}\frac{\partial\mathcal{W}(z_i\bar{\mathbf{u}};\mathbf{p})}{\partial\mathbf{p}}
$$<p>我们总结一下：</p>
<ol>
<li>$\frac{\partial g}{\partial y}$ 是体渲染过程的梯度</li>
<li>$\frac{\partial y}{\partial x}$ 是nerf的mlp神经网络的梯度</li>
<li>$\frac{\partial W}{\partial p}$ 是wrap变换的梯度</li>
</ol>


  </div>

  <footer class="post-footer">
    <ul class="post-tags">
      <li><a href="https://wangjv0812.github.io/WangJV-Blog-Pages/tags/%E8%AE%A1%E7%AE%97%E6%9C%BA%E8%A7%86%E8%A7%89/">计算机视觉</a></li>
      <li><a href="https://wangjv0812.github.io/WangJV-Blog-Pages/tags/%E7%A5%9E%E7%BB%8F%E6%B8%B2%E6%9F%93/">神经渲染</a></li>
      <li><a href="https://wangjv0812.github.io/WangJV-Blog-Pages/tags/3d%E9%87%8D%E5%BB%BA/">3D重建</a></li>
      <li><a href="https://wangjv0812.github.io/WangJV-Blog-Pages/tags/nerf/">NeRF</a></li>
    </ul>
<nav class="paginav">
  <a class="prev" href="https://wangjv0812.github.io/WangJV-Blog-Pages/2024/11/kalman_filter/">
    <span class="title">« Prev</span>
    <br>
    <span>Kalman_filter</span>
  </a>
  <a class="next" href="https://wangjv0812.github.io/WangJV-Blog-Pages/2024/07/hierarchical-gaussian-splatting/">
    <span class="title">Next »</span>
    <br>
    <span>Hierarchical Gaussian Splatting</span>
  </a>
</nav>

  </footer>
</article>
    </main>
    
<footer class="footer">
        <span>&copy; 2025 <a href="https://wangjv0812.github.io/WangJV-Blog-Pages/">WangJV Blog</a></span> · 

    <span>
        Powered by
        <a href="https://gohugo.io/" rel="noopener noreferrer" target="_blank">Hugo</a> &
        <a href="https://github.com/adityatelange/hugo-PaperMod/" rel="noopener" target="_blank">PaperMod</a>
    </span>
</footer>
<a href="#top" aria-label="go to top" title="Go to Top (Alt + G)" class="top-link" id="top-link" accesskey="g">
    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 12 6" fill="currentColor">
        <path d="M12 6H0l6-6z" />
    </svg>
</a>

<script>
    let menu = document.getElementById('menu')
    if (menu) {
        menu.scrollLeft = localStorage.getItem("menu-scroll-position");
        menu.onscroll = function () {
            localStorage.setItem("menu-scroll-position", menu.scrollLeft);
        }
    }

    document.querySelectorAll('a[href^="#"]').forEach(anchor => {
        anchor.addEventListener("click", function (e) {
            e.preventDefault();
            var id = this.getAttribute("href").substr(1);
            if (!window.matchMedia('(prefers-reduced-motion: reduce)').matches) {
                document.querySelector(`[id='${decodeURIComponent(id)}']`).scrollIntoView({
                    behavior: "smooth"
                });
            } else {
                document.querySelector(`[id='${decodeURIComponent(id)}']`).scrollIntoView();
            }
            if (id === "top") {
                history.replaceState(null, null, " ");
            } else {
                history.pushState(null, null, `#${id}`);
            }
        });
    });

</script>
<script>
    var mybutton = document.getElementById("top-link");
    window.onscroll = function () {
        if (document.body.scrollTop > 800 || document.documentElement.scrollTop > 800) {
            mybutton.style.visibility = "visible";
            mybutton.style.opacity = "1";
        } else {
            mybutton.style.visibility = "hidden";
            mybutton.style.opacity = "0";
        }
    };

</script>
<script>
    document.getElementById("theme-toggle").addEventListener("click", () => {
        if (document.body.className.includes("dark")) {
            document.body.classList.remove('dark');
            localStorage.setItem("pref-theme", 'light');
        } else {
            document.body.classList.add('dark');
            localStorage.setItem("pref-theme", 'dark');
        }
    })

</script>
<script>
    document.querySelectorAll('pre > code').forEach((codeblock) => {
        const container = codeblock.parentNode.parentNode;

        const copybutton = document.createElement('button');
        copybutton.classList.add('copy-code');
        copybutton.innerHTML = 'copy';

        function copyingDone() {
            copybutton.innerHTML = 'copied!';
            setTimeout(() => {
                copybutton.innerHTML = 'copy';
            }, 2000);
        }

        copybutton.addEventListener('click', (cb) => {
            if ('clipboard' in navigator) {
                navigator.clipboard.writeText(codeblock.textContent);
                copyingDone();
                return;
            }

            const range = document.createRange();
            range.selectNodeContents(codeblock);
            const selection = window.getSelection();
            selection.removeAllRanges();
            selection.addRange(range);
            try {
                document.execCommand('copy');
                copyingDone();
            } catch (e) { };
            selection.removeRange(range);
        });

        if (container.classList.contains("highlight")) {
            container.appendChild(copybutton);
        } else if (container.parentNode.firstChild == container) {
            
        } else if (codeblock.parentNode.parentNode.parentNode.parentNode.parentNode.nodeName == "TABLE") {
            
            codeblock.parentNode.parentNode.parentNode.parentNode.parentNode.appendChild(copybutton);
        } else {
            
            codeblock.parentNode.appendChild(copybutton);
        }
    });
</script>
</body>

</html>
